import base64
from collections import defaultdict
import json
import jsonpath
import logging
import re
from urllib import parse
from haralyzer import HarParser
import os
import requests
from urllib.parse import urlencode
import copy
import sys
from app.datafactory.utils import convert_list_to_dict,convert_x_www_form_urlencoded_to_dict
from app.datafactory.compat import urlparse
import time
try:
    from json.decoder import JSONDecodeError
except ImportError:
    JSONDecodeError = ValueError


IGNORE_REQUEST_HEADERS = [
    "host",
    "accept",
    "content-length",
    "connection",
    "accept-encoding",
    "accept-language",
    "origin",
    "referer",
    "cache-control",
    "pragma",
    # "cookie",
    "upgrade-insecure-requests",
    ":authority",
    ":method",
    ":scheme",
    ":path"
]


class Harparser(object):

    def __init__(self,  filter_str=None, exclude_str=None):

        self.filter_str = filter_str
        self.exclude_str = exclude_str or ""

    def __make_request_url(self, teststep_dict, entry_json):

        url = entry_json["request"].get("url")

        if not url:
            logging.exception("url missed in request.")
            sys.exit(1)
        if url.split('/')[-1].isdigit() :
            """
          请求一个资源路径  /api/apitest/projects/1

              Args:
                  entry_json (dict):
                      {
                          "request": {
                              "method": "GET",
                              "url": "http://neikong.ningdatech.com/ipc/api/v1/project/detail/2625",
                              "queryString": [],

                          },
                          "response": {...}
                      }


              Returns:
                  {
                      "request": {
                         "method": "GET",
                          "url": "http://neikong.ningdatech.com/ipc/api/v1/project/detail",
                          "payload": {"PathParameter":"2625"},
                      }
                  }

              """
            teststep_dict["url"] = '/'.join(url.split('/')[0:-1])
            teststep_dict["payload"]  = {"PathParameter": url.split('/')[-1]}

            """ parse HAR entry request url and queryString, and make teststep url and params

                    Args:
                        entry_json (dict):
                            {
                                "request": {
                                    "url": "https://httprunner.top/home?v=1&w=2",
                                    "queryString": [
                                        {"name": "v", "value": "1"},
                                        {"name": "w", "value": "2"}
                                    ],
                                },
                                "response": {}
                            }

                    Returns:
                        {
                            "name: "/home",
                            "request": {
                                url: "https://httprunner.top/home",
                                params: {"v": "1", "w": "2"}
                            }
                        }

                    """
        request_params = convert_list_to_dict(
        entry_json["request"].get("queryString", [])
    )

        parsed_object = urlparse.urlparse(url)
        if request_params:
            parsed_object = parsed_object._replace(query='')

            teststep_dict["url"] = parsed_object.geturl()
            teststep_dict["params"] = request_params
            teststep_dict["payload"] = {}
        else:
            teststep_dict["url"] = url
            teststep_dict["params"] = {}
            teststep_dict["payload"] = {}
        teststep_dict["data"] = {}
        return teststep_dict

        # teststep_dict["name"] = parsed_object.path

    def __make_request_method(self, teststep_dict, entry_json):
        """ parse HAR entry request method, and make teststep method.
        """
        method = entry_json["request"].get("method")
        if not method:
            logging.exception("method missed in request.")
            sys.exit(1)

        teststep_dict["method"] = method

    def __make_request_headers(self, teststep_dict, entry_json):
        """ parse HAR entry request headers, and make teststep headers.
            header in IGNORE_REQUEST_HEADERS will be ignored.

        Args:
            entry_json (dict):
                {
                    "request": {
                        "headers": [
                            {"name": "Host", "value": "httprunner.top"},
                            {"name": "Content-Type", "value": "application/json"},
                            {"name": "User-Agent", "value": "iOS/10.3"}
                        ],
                    },
                    "response": {}
                }

        Returns:
            {
                "request": {
                    headers: {"Content-Type": "application/json"}
            }

        """
        teststep_headers = {}
        for header in entry_json["request"].get("headers", []):
            if header["name"].lower() in IGNORE_REQUEST_HEADERS:
                continue

            teststep_headers[header["name"]] = header["value"]

        if teststep_headers:
            teststep_dict["headers"] = teststep_headers
        resheaders = {}
        for header in entry_json["response"].get("headers", []):
            if header["name"].lower() in IGNORE_REQUEST_HEADERS:
                continue

            resheaders[header["name"]] = header["value"]

        if resheaders:
            teststep_dict["resheaders"] = resheaders


    def _make_request_data(self, teststep_dict, entry_json):
        """ parse HAR entry request data, and make teststep request data

        Args:
            entry_json (dict):
                {
                    "request": {
                        "method": "POST",
                        "postData": {
                            "mimeType": "application/x-www-form-urlencoded; charset=utf-8",
                            "params": [
                                {"name": "a", "value": 1},
                                {"name": "b", "value": "2"}
                            }
                        },
                    },
                    "response": {...}
                }


        Returns:
            {
                "request": {
                    "method": "POST",
                    "data": {"v": "1", "w": "2"}
                }
            }

        """
        method = entry_json["request"].get("method")
        if method in ["POST", "PUT", "PATCH"]:
            postData = entry_json["request"].get("postData", {})
            mimeType = postData.get("mimeType")

            # Note that text and params fields are mutually exclusive.
            if "text" in postData:
                post_data = postData.get("text")
            else:
                params = postData.get("params", [])
                post_data = convert_list_to_dict(params)

            request_data_key = "data"
            if not mimeType:
                pass
            elif mimeType.startswith("application/json"):
                try:
                    post_data = json.loads(post_data)
                    request_data_key = "payload"
                except JSONDecodeError:
                    pass
            elif mimeType.startswith("application/x-www-form-urlencoded"):
                post_data = convert_x_www_form_urlencoded_to_dict(post_data)
            else:
                # TODO: make compatible with more mimeType
                pass

            teststep_dict[request_data_key] = post_data




    def _make_response_data(self, teststep_dict, entry_json):
        """ parse HAR entry request data, and make teststep request data

        Args:
            entry_json (dict):
                {
                    "request": {
                        "method": "POST",
                        "postData": {
                            "mimeType": "application/x-www-form-urlencoded; charset=utf-8",
                            "params": [
                                {"name": "a", "value": 1},
                                {"name": "b", "value": "2"}
                            }
                        },
                    },
                    "response": {...}
                }


        Returns:
            {
                "request": {
                    "method": "POST",
                    "data": {"v": "1", "w": "2"}
                }
            }

        """
        # method = entry_json["request"].get("method")
        # if method in ["POST", "PUT", "PATCH"]:
        content = entry_json["response"].get("content", {})

        mimeType = content.get("mimeType")

        # Note that text and params fields are mutually exclusive.
        if mimeType =="application/json" :
            if content.get("text"):
                res = json.loads(content.get("text"))
            else:
                res = {}
        else:
            params = content.get("params", [])
            res = convert_list_to_dict(params)

        # request_data_key = "data"
        # if not mimeType:
        #     pass
        # elif mimeType.startswith("application/json"):
        #     try:
        #         res = json.loads(res)
        #         request_data_key = "json"
        #     except JSONDecodeError:
        #         pass
        # elif mimeType.startswith("application/x-www-form-urlencoded"):
        #     res = utils.convert_x_www_form_urlencoded_to_dict(res)
        # else:
        #     # TODO: make compatible with more mimeType
        #     pass

        teststep_dict["res"] = res

    def har_reuqest(self,harfile):
        FILTER_MIME_TYPE = [
            'application/javascript',
            'text/css',
            'image/png',
            'image/jpeg',
            'image/jpg',
            'image/gif',
            'image/bmp',
            'image/webp',
            'image/x-icon',
            'image/vnd',
            'microsoft.icon',
        ]
        har_parser = HarParser(harfile)

        parsed_data = har_parser.har_data
        har_list = parsed_data["entries"]

        request_list = []
        index = 0
        for har in har_list:

            if har['response']['content'].get('mimeType') in FILTER_MIME_TYPE:
                continue
            index += 1
            request_info = {}

            self.__make_request_url(request_info,har)
            self.__make_request_method(request_info,har)
            self.__make_request_headers(request_info, har)
            self._make_request_data(request_info, har)
            self._make_response_data(request_info, har)
            #
            # url = har["request"]["url"]
            # # if url.startswith('http://shuiniche.ningdatech.com/cm/api/v1/user/auth/login/password'):
            # #     if len(har["request"]["cookies"])>0:
            # #         ND_JSESSION = har["response"]["cookies"][0]["value"]
            # #         print(har["request"])
            # #         XSRF_TOKEN = har["request"]["cookies"][0]["value"]
            # #         headers = "XSRF-TOKEN={}; ND_JSESSION={}".format(XSRF_TOKEN,ND_JSESSION)
            # #     else:
            # #         ND_JSESSION = har["response"]["cookies"][1]["value"]
            # #
            # #         XSRF_TOKEN = har["response"]["cookies"][0]["value"]
            # #         headers = "XSRF-TOKEN={}; ND_JSESSION={}".format(XSRF_TOKEN,ND_JSESSION)
            # # # # headers = flow.request.headers["Cookie"]
            # # else:
            # #     for header in  har["request"]["headers"]:
            # #         if header["name"]=="Cookie":
            # #             headers = ";".join(header["value"].split(";")[0:2])
            # request_info["index"] = index
            # request_info["url"] = url
            # request_info["method"] = method
            # request_info["headers"] = {header['name']: header['value'] for header in har['request']['headers']},
            # request_info["query"] = query
            # request_info['payload'] = 'json' if body_json[0] == 'json' else 'body',
            # request_info["data"] = body_json[1] if body_json[0] != 'file' else {},
            # request_info["res"] = res_data
            # request_info["title"] = harfile
            request_list.append(request_info)
        return request_list
    #
    # def _make_validate(self, teststep_dict, entry_json):
    #     """ parse HAR entry response and make teststep validate.
    #
    #     Args:
    #         entry_json (dict):
    #             {
    #                 "request": {},
    #                 "response": {
    #                     "status": 200,
    #                     "headers": [
    #                         {
    #                             "name": "Content-Type",
    #                             "value": "application/json; charset=utf-8"
    #                         },
    #                     ],
    #                     "content": {
    #                         "size": 71,
    #                         "mimeType": "application/json; charset=utf-8",
    #                         "text": "eyJJc1N1Y2Nlc3MiOnRydWUsIkNvZGUiOjIwMCwiTWVzc2FnZSI6bnVsbCwiVmFsdWUiOnsiQmxuUmVzdWx0Ijp0cnVlfX0=",
    #                         "encoding": "base64"
    #                     }
    #                 }
    #             }
    #
    #     Returns:
    #         {
    #             "validate": [
    #                 {"eq": ["status_code", 200]}
    #             ]
    #         }
    #
    #     """
    #     teststep_dict["validate"].append(
    #         {"eq": ["status_code", entry_json["response"].get("status")]}
    #     )
    #
    #     resp_content_dict = entry_json["response"].get("content")
    #
    #     headers_mapping = utils.convert_list_to_dict(
    #         entry_json["response"].get("headers", [])
    #     )
    #     if "Content-Type" in headers_mapping:
    #         teststep_dict["validate"].append(
    #             {"eq": ["headers.Content-Type", headers_mapping["Content-Type"]]}
    #         )
    #
    #     text = resp_content_dict.get("text")
    #     if not text:
    #         return
    #
    #     mime_type = resp_content_dict.get("mimeType")
    #     if mime_type and mime_type.startswith("application/json"):
    #
    #         encoding = resp_content_dict.get("encoding")
    #         if encoding and encoding == "base64":
    #             content = base64.b64decode(text).decode('utf-8')
    #         else:
    #             content = text
    #
    #         try:
    #             resp_content_json = json.loads(content)
    #         except JSONDecodeError:
    #             logging.warning(
    #                 "response content can not be loaded as json: {}".format(content.encode("utf-8"))
    #             )
    #             return
    #
    #         if not isinstance(resp_content_json, dict):
    #             return
    #
    #         for key, value in resp_content_json.items():
    #             if isinstance(value, (dict, list)):
    #                 continue
    #
    #             teststep_dict["validate"].append(
    #                 {"eq": ["content.{}".format(key), value]}
    #             )
    #
    # def _prepare_teststep(self, entry_json):
    #     """ extract info from entry dict and make teststep
    #
    #     Args:
    #         entry_json (dict):
    #             {
    #                 "request": {
    #                     "method": "POST",
    #                     "url": "https://httprunner.top/api/v1/Account/Login",
    #                     "headers": [],
    #                     "queryString": [],
    #                     "postData": {},
    #                 },
    #                 "response": {
    #                     "status": 200,
    #                     "headers": [],
    #                     "content": {}
    #                 }
    #             }
    #
    #     """
    #     teststep_dict = {
    #         "name": "",
    #         "request": {},
    #         "validate": []
    #     }
    #
    #     self.__make_request_url(teststep_dict, entry_json)
    #     self.__make_request_method(teststep_dict, entry_json)
    #     self.__make_request_headers(teststep_dict, entry_json)
    #     self._make_request_data(teststep_dict, entry_json)
    #     self._make_validate(teststep_dict, entry_json)
    #
    #     return teststep_dict
    #
    # def _prepare_config(self):
    #     """ prepare config block.
    #
    #     """
    #     return {
    #         "name": "testcase description",
    #         "variables": {}
    #     }
    #
    # def _prepare_teststeps(self, fmt_version):
    #     """ make teststep list.
    #         teststeps list are parsed from HAR log entries list.
    #
    #     """
    #     def is_exclude(url, exclude_str):
    #         exclude_str_list = exclude_str.split("|")
    #         for exclude_str in exclude_str_list:
    #             if exclude_str and exclude_str in url:
    #                 return True
    #
    #         return False
    #
    #     teststeps = []
    #     log_entries = utils.load_har_log_entries(self.har_file_path)
    #     for entry_json in log_entries:
    #         url = entry_json["request"].get("url")
    #         if self.filter_str and self.filter_str not in url:
    #             continue
    #
    #         if is_exclude(url, self.exclude_str):
    #             continue
    #
    #         if fmt_version == "v1":
    #             teststeps.append(
    #                 {"test": self._prepare_teststep(entry_json)}
    #             )
    #         else:
    #             # v2
    #             teststeps.append(
    #                 self._prepare_teststep(entry_json)
    #             )
    #
    #     return teststeps
    #
    # def _make_testcase(self, fmt_version):
    #     """ Extract info from HAR file and prepare for testcase
    #     """
    #     logging.debug("Extract info from HAR file and prepare for testcase.")
    #
    #     config = self._prepare_config()
    #     teststeps = self._prepare_teststeps(fmt_version)
    #
    #     if fmt_version == "v1":
    #         testcase = []
    #         testcase.append(
    #             {"config": config}
    #         )
    #         testcase.extend(teststeps)
    #     else:
    #         # v2
    #         testcase = {
    #             "config": config,
    #             "teststeps": teststeps
    #         }
    #
    #     return testcase
    #
    # def gen_testcase(self, file_type="JSON", fmt_version="v1"):
    #     harfile = os.path.splitext(self.har_file_path)[0]
    #     output_testcase_file = "{}.{}".format(harfile, file_type.lower())
    #
    #     logging.info("Start to generate testcase.")
    #     testcase = self._make_testcase(fmt_version)
    #     logging.debug("prepared testcase: {}".format(testcase))
    #
    #     if file_type == "JSON":
    #         utils.dump_json(testcase, output_testcase_file)
    #     else:
    #         utils.dump_yaml(testcase, output_testcase_file)

class Runscript(object):
    @classmethod
    def traverse_dict(cls,dictionary):
        # 执行表达式/函数
        # projectname:"{$Testname()}"
        # Cookies:"{$handlelogin($.method,$.url,$.headers.Cookie)}"
        # print(dictionary)
        step = copy.deepcopy(dictionary)
        if isinstance(dictionary, str):
            return dictionary

        for key, value in dictionary.items():
            # print(key)
            # print(value)
            if isinstance(value, dict):
                new_dict = {}
                for k, v in value.items():
                    v = Runscript.traverse_dict(value)
                    new_dict[k] = v
                return new_dict


            elif isinstance(value, list):
                if value == ['']:
                    return value
                new_list = []
                for i in value:
                    i = Runscript.traverse_dict(i)
                    new_list.append(i)
                return new_list

            elif isinstance(value, str) and value.startswith("{$") and value.endswith(")}"):

                expression = value.lstrip("{$").rstrip("}")

                try:
                    result = eval(expression)
                    dictionary[key] = result
                except Exception as e:
                    print(f"Error evaluating expression for key {key}: {e}")
            # else:
            #     return value
        return dictionary

    @classmethod
    def req(cls,case):
        # request_list = []

        for step in case:  # 用例列表

            step = Runscript.traverse_dict(step)

        return case

    @classmethod
    def dfs_dict(cls,value, case_list):
        # value : 当前值
        # data  : 路径列表
        # case_list : 数据列表
        # 判断value是否是路径字符串，如果是就用jsonpath 数据列表
        if isinstance(value, dict):  # 如果是字典 分解递归
            new_dict = {}
            for x, y in value.items():
                y = Runscript.dfs_dict(y, case_list)
                if isinstance(y, str):
                    if y.endswith("sameint"):

                        y = int(y.replace("sameint", ""))
                    elif y.endswith("originint"):
                        y = int(y.replace("originint", ""))
                new_dict[x] = y
            return new_dict
        if isinstance(value, list):  # 如果是列表 分解递归
            new_list = []
            for i in value:
                i = Runscript.dfs_dict(i, case_list)
                new_list.append(i)
            return new_list
        if isinstance(value, str) and value.startswith("$."):
            try:

                flag = jsonpath.jsonpath(case_list, value)[0]
            except Exception as e:
                try:

                    if isinstance(value, str) and value.endswith('originint'):
                        value = value.replace("originint", '')

                        flag = jsonpath.jsonpath(case_list, value)[0]
                        if isinstance(flag, str) and value.endswith('originint'):
                            flag = int(flag)
                    elif isinstance(value, str) and value.endswith('sameint'):
                        value = value.replace("sameint", '')
                        flag = jsonpath.jsonpath(case_list, value)[0]
                        if isinstance(flag, str) and value.endswith('sameint'):
                            flag = int(flag)

                    flag = jsonpath.jsonpath(case_list, value)[0]
                except Exception as f:
                    a = value.split('.')
                    print(f"第{a[1]}个请求的{a[2]}的为", case_list[int(a[1])])
                    print("报错的value", value)

            if isinstance(flag, str) and flag.startswith("$."):
                flag = Runscript.dfs_dict(flag, case_list)
                return flag
            else:
                return flag

        else:
            return value
    @classmethod
    def a(cls,case_list):  # 路径 -》值
        # case_path_list ---- 大列表
        # case_list   ----小列表
        case_list1 = copy.deepcopy(case_list)

        for step in case_list:
            for key, value in step.items():
                # print("key", key)
                # 路径转化值

                value = Runscript.dfs_dict(value, case_list)

                step[key] = value
        return case_list, case_list1
    @classmethod
    def run_case(cls,script):

        case_list = Runscript.req(script)

        case_list = [case_list]
        case_list_model = copy.deepcopy(case_list)
        case_num = 0
        for case_model in case_list_model:
            # print("case_model",case_model)
            case_num += 1
            index = 0
            case_dfs_list, case_model = Runscript.a(copy.deepcopy(case_model))

            # case_dfs_list ==全都是数据没有路径
            # case_model --有路径
            # print("case_dfs_list0", case_dfs_list)
            for step, step_mode in zip(range(0, len(case_dfs_list)), range(0, len(case_model))):  # 发送请求，替换res
                index += 1

                url = case_dfs_list[step]["url"]
                # if url == 'http://neikong.ningdatech.com/ipc/api/v1/annual-approval-plan/save':
                #     time.sleep(25)
                headers = case_dfs_list[step]["headers"]
                method = case_dfs_list[step]["method"]
                # if method =='post':
                #     time.sleep(5)
                # if url == 'http://neikong.ningdatech.com/ipc/api/v1/funds-summary/upload-result':
                #     time.sleep(10)
                params = case_dfs_list[step]["payload"]
                payload = case_dfs_list[step]["payload"]
                if method == "get":
                    encoded_payload = urlencode(payload)
                    url = url + '?' + encoded_payload
                    # print(url)
                # if method =="post" and payload.key() == "params":
                #     url = url+payload["params"]
                if isinstance(payload, dict):
                    if "PathParameter" in payload.keys():
                        url = url.split('/')[:-1]

                        url = "/".join(url) + '/' + str(payload["PathParameter"])
                        print(url)
                if params:
                    url = url + "?"+parse.urlencode(params)
                #             #执行的时候用step里的数据
                case_model[step_mode]["payload"] = payload
                case_model[step_mode]["url"] = url
                case_model[step_mode]["method"] = method
                print(payload)
                if payload.get("scrip"):
                    eval(payload["scrip"])

                for i in range(0, 12):

                    res = requests.request(method=method, url=url, json=payload, headers=headers)  # 运行
                    print(f"当前第{i + 1}次执行第{case_num}个用例的{index}个步骤,url为{url}")
                    print(payload)
                    print(res.text)

                    if "code" not in res.json() or res.json()["code"] == 200:
                        print(res.json())
                        break
                    time.sleep(5)

                case_model[step_mode]["res"] = res.json()

                case_dfs_list, case_model = Runscript.a(copy.deepcopy(case_model))

if __name__ == '__main__':
    from apitest.test1 import req3
    # for req in req1:
    #     req["headers"] = json.(req["headers"])
    case = Runscript.run_case(req3)
    print(case)